﻿// --------------------------------------------------------------------------------------------------------------------
// <copyright file="MetroSkeletonDisplayManager.cs" company="arvystate.net">
//   arvystate.net
// </copyright>
// <summary>
//   Used to draw Skeleton on image
// </summary>
// --------------------------------------------------------------------------------------------------------------------

namespace PresentationAid.Lib
{
    using System;
    using System.Collections.Generic;
    using System.Globalization;
    using System.Linq;
    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Documents;
    using System.Windows.Media;
    using System.Windows.Shapes;

    using Kinect.Toolbox;

    using Microsoft.Kinect;

    using NLog;

    using PresentationAid.Core;
    using PresentationAid.Lib.Enums;
    using PresentationAid.Settings;

    /// <summary>
    /// Used to draw Skeleton on image
    /// </summary>
    public class MetroSkeletonDisplayManager
    {
        #region Static Fields

        /// <summary>
        /// The logger.
        /// </summary>
        private static readonly Logger Logger = LogManager.GetCurrentClassLogger();

        #endregion

        #region Fields

        /// <summary>
        /// The _root canvas.
        /// </summary>
        private readonly Canvas rootCanvas;

        /// <summary>
        /// The _sensor.
        /// </summary>
        private readonly KinectSensor sensor;

        /// <summary>
        /// The _control brush.
        /// </summary>
        private SolidColorBrush controlBrush;

        /// <summary>
        /// The _highlight brush.
        /// </summary>
        private SolidColorBrush highlightBrush;

        #endregion

        #region Constructors and Destructors

        /// <summary>
        /// Initializes a new instance of the <see cref="MetroSkeletonDisplayManager"/> class.
        /// </summary>
        /// <param name="kinectSensor">
        /// The kinect sensor.
        /// </param>
        /// <param name="root">
        /// The root.
        /// </param>
        /// <param name="highlightBrush">
        /// The highlight brush.
        /// </param>
        /// <param name="controlBrush">
        /// The control brush.
        /// </param>
        public MetroSkeletonDisplayManager(
            KinectSensor kinectSensor, Canvas root, SolidColorBrush highlightBrush, SolidColorBrush controlBrush)
        {
            this.rootCanvas = root;
            this.sensor = kinectSensor;
            this.highlightBrush = highlightBrush;
            this.controlBrush = controlBrush;

            Logger.Trace("Init: MetroSkeletonDisplayManager");
        }

        #endregion

        #region Public Methods and Operators

        /// <summary>
        /// The draw.
        /// </summary>
        /// <param name="skeletons">
        /// The skeletons.
        /// </param>
        /// <param name="seated">
        /// The seated.
        /// </param>
        public void Draw(Skeleton[] skeletons, bool seated)
        {
            if (skeletons.All(s => s.TrackingState == SkeletonTrackingState.NotTracked))
            {
                this.rootCanvas.Children.Clear();

                return;
            }

            this.rootCanvas.Children.Clear();

            foreach (Skeleton skeleton in skeletons)
            {
                if (skeleton.TrackingState != SkeletonTrackingState.Tracked)
                {
                    continue;
                }

                SolidColorBrush drawBrush = this.highlightBrush;

                if (KinectSensorManager.Instance.GestureEngine.IsInControl(skeleton.TrackingId)
                    && (KinectSensorManager.Instance.GestureEngine.ControlGesture != GestureCommand.None))
                {
                    drawBrush = this.controlBrush;
                }

                this.Plot(JointType.HandLeft, skeleton.Joints, drawBrush);
                this.Trace(JointType.HandLeft, JointType.WristLeft, skeleton.Joints, drawBrush);
                this.Plot(JointType.WristLeft, skeleton.Joints, drawBrush);
                this.Trace(JointType.WristLeft, JointType.ElbowLeft, skeleton.Joints, drawBrush);
                this.Plot(JointType.ElbowLeft, skeleton.Joints, drawBrush);
                this.Trace(JointType.ElbowLeft, JointType.ShoulderLeft, skeleton.Joints, drawBrush);
                this.Plot(JointType.ShoulderLeft, skeleton.Joints, drawBrush);
                this.Trace(JointType.ShoulderLeft, JointType.ShoulderCenter, skeleton.Joints, drawBrush);
                this.Plot(JointType.ShoulderCenter, skeleton.Joints, drawBrush);

                this.Trace(JointType.ShoulderCenter, JointType.Head, skeleton.Joints, drawBrush);

                this.Plot(JointType.Head, JointType.ShoulderCenter, skeleton.Joints, drawBrush);

                if (SettingManager.Instance.DebugMode)
                {
                    this.Text(JointType.Head, JointType.ShoulderCenter, skeleton.Joints, skeleton.TrackingId, drawBrush);
                }

                this.Trace(JointType.ShoulderCenter, JointType.ShoulderRight, skeleton.Joints, drawBrush);
                this.Plot(JointType.ShoulderRight, skeleton.Joints, drawBrush);
                this.Trace(JointType.ShoulderRight, JointType.ElbowRight, skeleton.Joints, drawBrush);
                this.Plot(JointType.ElbowRight, skeleton.Joints, drawBrush);
                this.Trace(JointType.ElbowRight, JointType.WristRight, skeleton.Joints, drawBrush);
                this.Plot(JointType.WristRight, skeleton.Joints, drawBrush);
                this.Trace(JointType.WristRight, JointType.HandRight, skeleton.Joints, drawBrush);
                this.Plot(JointType.HandRight, skeleton.Joints, drawBrush);

                if (!seated)
                {
                    this.Trace(JointType.ShoulderCenter, JointType.Spine, skeleton.Joints, drawBrush);
                    this.Plot(JointType.Spine, skeleton.Joints, drawBrush);
                    this.Trace(JointType.Spine, JointType.HipCenter, skeleton.Joints, drawBrush);
                    this.Plot(JointType.HipCenter, skeleton.Joints, drawBrush);

                    this.Trace(JointType.HipCenter, JointType.HipLeft, skeleton.Joints, drawBrush);
                    this.Plot(JointType.HipLeft, skeleton.Joints, drawBrush);
                    this.Trace(JointType.HipLeft, JointType.KneeLeft, skeleton.Joints, drawBrush);
                    this.Plot(JointType.KneeLeft, skeleton.Joints, drawBrush);
                    this.Trace(JointType.KneeLeft, JointType.AnkleLeft, skeleton.Joints, drawBrush);
                    this.Plot(JointType.AnkleLeft, skeleton.Joints, drawBrush);
                    this.Trace(JointType.AnkleLeft, JointType.FootLeft, skeleton.Joints, drawBrush);
                    this.Plot(JointType.FootLeft, skeleton.Joints, drawBrush);

                    this.Trace(JointType.HipCenter, JointType.HipRight, skeleton.Joints, drawBrush);
                    this.Plot(JointType.HipRight, skeleton.Joints, drawBrush);
                    this.Trace(JointType.HipRight, JointType.KneeRight, skeleton.Joints, drawBrush);
                    this.Plot(JointType.KneeRight, skeleton.Joints, drawBrush);
                    this.Trace(JointType.KneeRight, JointType.AnkleRight, skeleton.Joints, drawBrush);
                    this.Plot(JointType.AnkleRight, skeleton.Joints, drawBrush);
                    this.Trace(JointType.AnkleRight, JointType.FootRight, skeleton.Joints, drawBrush);
                    this.Plot(JointType.FootRight, skeleton.Joints, drawBrush);
                }
            }
        }

        /// <summary>
        /// The set control brush.
        /// </summary>
        /// <param name="brush">
        /// The brush.
        /// </param>
        public void SetControlBrush(SolidColorBrush brush)
        {
            this.controlBrush = brush;
        }

        /// <summary>
        /// The set highlight brush.
        /// </summary>
        /// <param name="brush">
        /// The brush.
        /// </param>
        public void SetHighlightBrush(SolidColorBrush brush)
        {
            this.highlightBrush = brush;
        }

        #endregion

        #region Methods

        /// <summary>
        /// The get coordinates.
        /// </summary>
        /// <param name="jointType">
        /// The joint type.
        /// </param>
        /// <param name="joints">
        /// The joints.
        /// </param>
        /// <param name="x">
        /// The x.
        /// </param>
        /// <param name="y">
        /// The y.
        /// </param>
        private void GetCoordinates(JointType jointType, IEnumerable<Joint> joints, out float x, out float y)
        {
            Joint joint = joints.First(j => j.JointType == jointType);

            Vector2 vector2 = Tools.Convert(this.sensor, joint.Position);

            x = (float)(vector2.X * this.rootCanvas.ActualWidth);
            y = (float)(vector2.Y * this.rootCanvas.ActualHeight);
        }

        /// <summary>
        /// The plot.
        /// </summary>
        /// <param name="centerId">
        /// The center id.
        /// </param>
        /// <param name="joints">
        /// The joints.
        /// </param>
        /// <param name="brush">
        /// The brush.
        /// </param>
        private void Plot(JointType centerId, IEnumerable<Joint> joints, SolidColorBrush brush)
        {
            float centerX;
            float centerY;

            this.GetCoordinates(centerId, joints, out centerX, out centerY);

            const double Diameter = 8;

            Ellipse ellipse = new Ellipse
                                  {
                                      Width = Diameter, 
                                      Height = Diameter, 
                                      HorizontalAlignment = HorizontalAlignment.Left, 
                                      VerticalAlignment = VerticalAlignment.Top, 
                                      StrokeThickness = 4.0, 
                                      Stroke = brush, 
                                      StrokeLineJoin = PenLineJoin.Round
                                  };

            Canvas.SetLeft(ellipse, centerX - (ellipse.Width / 2));
            Canvas.SetTop(ellipse, centerY - (ellipse.Height / 2));

            this.rootCanvas.Children.Add(ellipse);
        }

        /// <summary>
        /// The plot.
        /// </summary>
        /// <param name="centerId">
        /// The center id.
        /// </param>
        /// <param name="baseId">
        /// The base id.
        /// </param>
        /// <param name="joints">
        /// The joints.
        /// </param>
        /// <param name="brush">
        /// The brush.
        /// </param>
        private void Plot(JointType centerId, JointType baseId, JointCollection joints, SolidColorBrush brush)
        {
            float centerX;
            float centerY;

            this.GetCoordinates(centerId, joints, out centerX, out centerY);

            float baseX;
            float baseY;

            this.GetCoordinates(baseId, joints, out baseX, out baseY);

            double diameter = Math.Abs(baseY - centerY);

            Ellipse ellipse = new Ellipse
                                  {
                                      Width = diameter, 
                                      Height = diameter, 
                                      HorizontalAlignment = HorizontalAlignment.Left, 
                                      VerticalAlignment = VerticalAlignment.Top, 
                                      StrokeThickness = 4.0, 
                                      Stroke = brush, 
                                      StrokeLineJoin = PenLineJoin.Round
                                  };

            Canvas.SetLeft(ellipse, centerX - (ellipse.Width / 2));
            Canvas.SetTop(ellipse, centerY - (ellipse.Height / 2));

            this.rootCanvas.Children.Add(ellipse);
        }

        /// <summary>
        /// The text.
        /// </summary>
        /// <param name="centerId">
        /// The center id.
        /// </param>
        /// <param name="baseId">
        /// The base id.
        /// </param>
        /// <param name="joints">
        /// The joints.
        /// </param>
        /// <param name="id">
        /// The id.
        /// </param>
        /// <param name="brush">
        /// The brush.
        /// </param>
        private void Text(JointType centerId, JointType baseId, JointCollection joints, int id, SolidColorBrush brush)
        {
            TextBlock text = new TextBlock(new Run(id.ToString(CultureInfo.InvariantCulture)))
                                 {
                                     FontSize = 24, 
                                     FontWeight =
                                         FontWeights
                                         .SemiBold, 
                                     Foreground = brush
                                 };

            if (KinectSensorManager.Instance.GestureEngine.ControlId == id)
            {
            }

            float centerX;
            float centerY;

            this.GetCoordinates(centerId, joints, out centerX, out centerY);

            float baseX;
            float baseY;

            this.GetCoordinates(baseId, joints, out baseX, out baseY);

            double diameter = Math.Abs(baseY - centerY);

            Canvas.SetLeft(text, centerX + diameter + 5);
            Canvas.SetTop(text, centerY);

            this.rootCanvas.Children.Add(text);
        }

        /// <summary>
        /// The trace.
        /// </summary>
        /// <param name="sourceId">
        /// The source id.
        /// </param>
        /// <param name="destinationId">
        /// The destination id.
        /// </param>
        /// <param name="joints">
        /// The joints.
        /// </param>
        /// <param name="brush">
        /// The brush.
        /// </param>
        private void Trace(JointType sourceId, JointType destinationId, JointCollection joints, SolidColorBrush brush)
        {
            float sourceX;
            float sourceY;

            this.GetCoordinates(sourceId, joints, out sourceX, out sourceY);

            float destinationX;
            float destinationY;

            this.GetCoordinates(destinationId, joints, out destinationX, out destinationY);

            Line line = new Line
                            {
                                X1 = sourceX, 
                                Y1 = sourceY, 
                                X2 = destinationX, 
                                Y2 = destinationY, 
                                HorizontalAlignment = HorizontalAlignment.Left, 
                                VerticalAlignment = VerticalAlignment.Top, 
                                StrokeThickness = 4.0, 
                                Stroke = brush, 
                                StrokeLineJoin = PenLineJoin.Round
                            };

            this.rootCanvas.Children.Add(line);
        }

        #endregion
    }
}